package internal

import (
	"context"
	"errors"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"time"
)

// CreateFile creates a file with the given parent path and file name. If the parent directory does not exist, it will be created. If the file already exists, no error is returned.
func CreateFile(parentPath, fileName string) error {
	// 创建目录
	if _, err := os.Stat(parentPath); os.IsNotExist(err) {
		// 文件不存在，创建文件
		err := os.MkdirAll(parentPath, os.ModePerm)
		return err
	}

	// 创建文件
	fileAbsPath := path.Join(parentPath, fileName)
	if _, err := os.Stat(fileAbsPath); os.IsNotExist(err) {
		_, err := os.Create(fileAbsPath)
		return err
	}

	return nil
}

// ReadFile reads the contents of the file at the given file path and returns the data and any error that occurred.
func ReadFile(filePath string) ([]byte, error) {
	data, err := os.ReadFile(filePath)
	if err != nil {
		return nil, err
	}

	return data, nil
}

// SplitString splits the given string `str` into a slice of strings using the provided separator `sep`.
func SplitString(str string, sep string) []string {
	return strings.Split(str, sep)
}

// CheckIsContinuousCmd checks if the given command is a continuous command based on the provided continuous_cmds map.
// It first splits the command into individual words, and then checks if the first word or the first two words together
// are present in the continuous_cmds map. If any of the options for the command are found in the command string, the
// function returns true, indicating that the command is a continuous command in linux.
func CheckIsContinuousCmd(command string, continuous_cmds map[string][]string) bool {
	cmds := SplitString(command, " ")
	if len(cmds) == 0 {
		return false
	}

	_hCmds := make([]string, 0, len(cmds))
	for _, v := range cmds {
		// 去除单个指令前后空格
		_hCmds = append(_hCmds, strings.Trim(v, " "))
	}

	var check func(string) bool = func(s string) bool {
		if options, ok := continuous_cmds[s]; ok {
			for _, opt := range options {
				if strings.Contains(command, opt) {
					return true
				}
			}

			if len(options) == 0 {
				return true
			}
		}

		return false
	}

	// 判断单个命令名称
	cmd_name := cmds[0]
	res := check(cmd_name)

	// 判断多个命令名称
	if len(_hCmds) > 1 {
		if !res {
			cmd_name = strings.Join(_hCmds[:2], " ")
		}
	}
	res = check(cmd_name)

	return res
}

func _doShell(exes []string, cmd string) (string, error) {
	if strings.TrimSpace(cmd) == "" {
		return "", errors.New("empty command provided")
	}

	arg1, arg2 := exes[0], exes[1]
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// 创建一个命令
	cmdObj := exec.CommandContext(ctx, arg1, arg2, cmd)
	// 执行命令
	output, err := cmdObj.CombinedOutput()
	if err != nil {
		if ctx.Err() == context.DeadlineExceeded {
			return "", errors.New("command timed out after 30 seconds")
		}

		// 可能也有用
		return string(output), nil
	}

	return string(output), nil
}

// DoWindowsCmdExec executes a command in the Windows PowerShell and returns the combined output and any error that occurred.
// The _cmd parameter is the command to be executed in the PowerShell.
// The function returns the output of the command as a string, and any error that occurred during the execution.
func DoWindowsCmdExec(_cmd string) (string, error) {
	return _doShell([]string{"powershell.exe", "-Command"}, _cmd)
}

// DoUnixCmdExec executes a command in the Unix shell (using /bin/bash -c) and returns the combined output and any error that occurred.
// The _cmd parameter is the command to be executed in the shell.
// The function returns the output of the command as a string, and any error that occurred during the execution.
func DoUnixCmdExec(_cmd string) (string, error) {
	return _doShell([]string{"/bin/bash", "-c"}, _cmd)
}

// ParseHostPort splits a host:port string into separate host and port components.
// It returns an error if the input does not contain a colon separator.
// The first return value is the host, the second is the port, and the third is any error encountered.
func ParseHostPort(hostPort string) (string, int, error) {
	host, portStr, found := strings.Cut(hostPort, ":")
	if !found {
		return "", 0, errors.New("invalid host:port format")
	} else {
		portStr2Int, err := strconv.Atoi(portStr)
		if err != nil {
			return "", 0, err
		}
		return host, portStr2Int, nil
	}
}
